package simple.engine;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import simple.engine.common.SimpleOptions;
import simple.engine.managers.ScreenManager;
import android.app.Activity;
import android.content.pm.ActivityInfo;
import android.opengl.GLSurfaceView;
import android.opengl.GLSurfaceView.Renderer;
import android.opengl.GLU;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.Window;
import android.view.WindowManager;

/**
 * SimpleEngine class
 * You should extend your default activity with this 
 * @author mbienkowski.com
 */
public abstract class SimpleEngine extends Activity implements Renderer{
	
	private 	GLSurfaceView 		mView;
	
	/* Touch vars */
	private 	float				mLastTouchX;
	private 	float				mLastTouchY;
	
	private 	float				mTouchOriginX;
	private 	float				mTouchOriginY;
	private 	long				mTouchOriginTime;
	private 	boolean				mHoldManaged = false;
	
	private		static final int 	mInvalidPointerID = -1;
	private 	int 				mActivePointerId = mInvalidPointerID;
	
	/* Time control vars */
	
	private long mGameTimeControl;
	private long mLastUpdateTime;
	
	private long mTPF;
	private long mAccumulatedTime;
	private long mMaxAccumulatedTime;
	
	// Construtor
	
	public SimpleEngine()
	{
		mGameTimeControl 	= 0;
		mLastUpdateTime 	= System.currentTimeMillis();
		
		mTPF 				= 1000/SimpleOptions.getDesiredFPS(); // 1000 / fps = time per frame
		mAccumulatedTime 	= 0;
		mMaxAccumulatedTime = 1000; // 1000 ms = 1 sec
	}
	
    @Override
    public void onCreate(Bundle savedInstanceState) {
        this.requestWindowFeature(Window.FEATURE_NO_TITLE);
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
        
        if(SimpleOptions.getFullScreen()) getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
        
        mView = new GLSurfaceView(this);
        mView.setRenderer(this);
        
        setContentView(mView);
        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
        
        super.onCreate(savedInstanceState);
    }
    
    @Override
    protected void onDestroy() {
    	// TODO Auto-generated method stub
    	this.onAppDestroy();
    	super.onDestroy();
    }
    
    // Time control methods
    
    private void gameLoopTimeControl()
	{
		mGameTimeControl = System.currentTimeMillis() - mLastUpdateTime;
		mLastUpdateTime += mGameTimeControl;
		mGameTimeControl = Math.max(0, mGameTimeControl);
		mAccumulatedTime += mGameTimeControl;
		mAccumulatedTime = clamp(mAccumulatedTime, 0, mMaxAccumulatedTime);
	}
	
	private long clamp(float what, int a, float b) {
		return (long) Math.min(b, Math.max(what, a));
	}
    
    // Renderer methods
    
	public void onSurfaceCreated(GL10 gl, EGLConfig config) 
	{
		gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
		gl.glClearDepthf(1.0f);
		gl.glEnable(GL10.GL_TEXTURE_2D);
		gl.glDepthFunc(GL10.GL_LEQUAL);
		gl.glEnable(GL10.GL_DEPTH_TEST);
		gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);
		gl.glEnable(GL10.GL_BLEND);
		gl.glDisable(GL10.GL_COLOR_MATERIAL);
		gl.glShadeModel(GL10.GL_SMOOTH);
		gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_NICEST); 
		this.onAppCreate(gl);
	}
	
	public void onSurfaceChanged(GL10 gl, int width, int height) 
	{
		ScreenManager.setScreenConfig(SimpleOptions.getDesiredWidth(), SimpleOptions.getDesiredHeight(), width, height);

		gl.glViewport(ScreenManager.getOffsetX(), ScreenManager.getOffsetY(), ScreenManager.getScaledWidth(), ScreenManager.getScaledHeight());
	    gl.glMatrixMode(GL10.GL_PROJECTION);
		gl.glLoadIdentity();
		GLU.gluPerspective(gl, 90.0f, (float) SimpleOptions.getDesiredWidth() / (float)SimpleOptions.getDesiredHeight(), 0.01f, Float.MAX_VALUE);
		gl.glMatrixMode(GL10.GL_MODELVIEW);
	}
 
	public void onDrawFrame(GL10 gl) 
	{
		// We will use this method as game loop
		gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
	    gl.glLoadIdentity();
	    gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
	    
		gameLoopTimeControl();
		
		while(mAccumulatedTime >= mTPF)
		{
			onGameUpdate(mGameTimeControl);
			mAccumulatedTime -= mTPF;
		}
		
		this.onGameDraw(gl);
	}

	@Override
	public boolean onTouchEvent(MotionEvent event){
		if(!ScreenManager.isClickableArea(event.getX(), event.getY())) return super.onTouchEvent(event);;
			
		if(mTouchOriginTime == 0)
		{ 
			mTouchOriginTime = System.currentTimeMillis();
			mTouchOriginX = event.getX();
			mTouchOriginY = event.getY();
		}
		else if(Math.abs(event.getX() - mTouchOriginX) < 10 &&  Math.abs(event.getY() - mTouchOriginY) < 10)
		{
			this.onPointerHold(mTouchOriginX, mTouchOriginY, System.currentTimeMillis() - mTouchOriginTime);
		}
		
		try
		{
			this.onPointerTouch(event);
			
			final int action = event.getAction();
		    switch (action & MotionEvent.ACTION_MASK) {
		   
			    case MotionEvent.ACTION_MOVE: 
			    {
			        final int pointerIndex = event.findPointerIndex(mActivePointerId);
			        final float x = event.getX(pointerIndex);
			        final float y = event.getY(pointerIndex);
		
			        this.onPointerMove(x,y, mLastTouchX, mLastTouchY);
			        
			        mLastTouchX = x;
			        mLastTouchY = y;
			        break;
			    }
			    
			    case MotionEvent.ACTION_DOWN: 
			    {
			        final float x = event.getX();
			        final float y = event.getY();
			        
			        this.onPointerDown(x,y);
			        
			        mLastTouchX = x;
			        mLastTouchY = y;
			        
			        mActivePointerId = event.getPointerId(0);
			        break;
			    }
			    
			    case MotionEvent.ACTION_UP: 
			    {
			        mActivePointerId = mInvalidPointerID;
			        mTouchOriginTime = 0;
			        mHoldManaged = false;
			        this.onPointerUp();
			        break;
			    }
			        
			    case MotionEvent.ACTION_CANCEL: 
			    {
			        mActivePointerId = mInvalidPointerID;
			        break;
			    }
		    }
			
			mLastTouchX = event.getX();
	        mLastTouchY = event.getY();
		}
		catch(Exception e)
		{
		}
		
		return super.onTouchEvent(event);
	}
	
	// Geters and setters
	
	/**
	 * Set the pointerHoldManged variable
	 * If it's true you will method onPointerHold will stop calling until you touch the screen again.
	 * @param value True if yes, otherwise false
	 */
	public void setPointerHoldManaged(boolean value)
	{
		mHoldManaged = value;
	}
	
	/**
	 * Tells you if PointerHold event was managed or not
	 * @return True of false 
	 */
	public boolean getPointerHoldManaged()
	{
		return mHoldManaged;
	}
	
	// Abstract methods
	
	/**
	 * Called after activity method onSurfaceCreated.
	 * You should load here everything that you want to use later
	 * @param gl the GL interface
	 */
	protected abstract void onAppCreate(GL10 gl);
	
	/**
	 * Called on activity method onDestroy.
	 * You should clean some memory here
	 */
	protected abstract void onAppDestroy();
	
	/**
	 * Called every time when when game needs to update logic.
	 * You should use it for update physics, object positions etc.
	 * @param gtc Game Time Control variable. Used to synchonize animations.
	 */
	protected abstract void onGameUpdate(float gtc);
	
	/**
	 * Called all the time.
	 * You should use it ONLY to draw elements.
	 * @param gl the GL interface
	 */
	protected abstract void onGameDraw(GL10 gl);
	
	/**
	 * Called all the time while you touch the screen
	 * @param event {@link MotionEvent}
	 */
	protected abstract void onPointerTouch(MotionEvent event);
	
	/**
	 * Called only once on action pointer down.
	 * @param x The x coordinate of touch (Screen coords)
	 * @param y The x coordinate of touch (Screen coords)
	 */
	protected abstract void onPointerDown(float x, float y);
	
	/**
	 * Called while you touch the screen without moving pointer (only if pointer hold is not managed).
	 * @param x The x coordinate of touch (screen coords)
	 * @param y The x coordinate of touch (screen coords)
	 * @param ms The time of hold in miliseconds
	 */
	protected abstract void onPointerHold(float x, float y, long ms);
	
	/**
	 * Called while you move pointer on the screen
	 * @param x The actual x coordinate of touch (screen coords)
	 * @param y The actual x coordinate of touch (screen coords)
	 * @param lastX The historical x coordinate of touch (screen coords)
	 * @param lastY The historical x coordinate of touch (screen coords)
	 */
	protected abstract void onPointerMove(float x, float y, float lastX, float lastY);
	
	/**
	 * Called when you get off your finder from the screen 
	 */
	protected abstract void onPointerUp();
}



